package easypal.brilliard.world.brilliards.logic;

import java.util.LinkedList;
import java.util.List;

import easypal.brilliard.util.TraceUtil;

public class HitStatemachineImpl implements HitStatemachine {
	
	protected PlayerViewFromHitStatemachine playerOne;
	protected PlayerViewFromHitStatemachine playerTwo;
	protected PlayerViewFromHitStatemachine currentPlayer;
	protected PlayerViewFromHitStatemachine lastPlayer;
	
	protected boolean flagOfWhiteBallEntered;
	
	protected List<BallColor> ballsThatNeedBeBack = new LinkedList<BallColor>();
	
	protected int currentHitScore = 0;
	
	protected boolean isThereAnyBallExceptWhite;
	
	
	/* The variable members need be initialized before using this object. */
	// Hit machine state
	protected HitState state = HitState.begin;
	
	// number of red balls left
	protected int numOfRedBallLeft = 15;
	// Is there color ball?
	protected boolean isThereGreenBall = true;
	protected boolean isThereYellowBall = true;
	protected boolean isThereBrownBall = true;
	protected boolean isThereBlueBall = true;
	protected boolean isTherePinkBall = true;
	protected boolean isThereBlackBall = true;
	

	@Override
	public void setPlayerOne(PlayerViewFromHitStatemachine p) {
		this.playerOne = p;

	}

	@Override
	public void setPlayerTwo(PlayerViewFromHitStatemachine p) {
		this.playerTwo = p;

	}

	@Override
	public PlayerViewFromHitStatemachine getPlayerOne() {
		
		return this.playerOne;
	}

	@Override
	public PlayerViewFromHitStatemachine getPlayerTwo() {

		return this.playerTwo;
	}

	@Override
	public PlayerViewFromHitStatemachine getCurrentPlayer() {

		return this.currentPlayer;
	}

	@Override
	public PlayerViewFromHitStatemachine getLastPlayer() {

		return this.lastPlayer;
	}
	
	@Override
	public PlayerViewFromHitStatemachine getAnotherPlayer() {
		if (this.getCurrentPlayer() == this.getPlayerOne())
			return this.getPlayerTwo();
		else 
			return this.getPlayerOne();
	}

	@Override
	public boolean doesWhiteBallEnter() {
		
		return this.flagOfWhiteBallEntered;
	}

	@Override
	public List<BallColor> getColorBallsThatShouldBeBack() {
		return this.ballsThatNeedBeBack;
	}

	@Override
	public int getCurrentHitScore() {
		return this.currentHitScore;
	}

	@Override
	public void begin() {
		this.currentPlayer = this.playerOne;
		state = HitState.red;
		
		
		// number of red balls left
		numOfRedBallLeft = 15;
		// Is there color ball?
		isThereGreenBall = true;
		isThereYellowBall = true;
		isThereBrownBall = true;
		isThereBlueBall = true;
		isTherePinkBall = true;
		isThereBlackBall = true;
	}
	
	@Override
	public boolean isOver() {
		return (state == HitState.end);
	}

	@Override
	public void hit(HitRecord hitRecord) {
		/* Trace some info so I can't know little. */
		System.out.println("Last machine state: " + state);
		if (this.getCurrentPlayer() == this.playerOne)
			System.out.println("Last player: player one");
		else
			System.out.println("Last player: player two");
		/***********************************************************/
		
		/* Update last player. */
		this.lastPlayer = this.getCurrentPlayer();
		
		boolean fault = checkFaultAndComputeScore(hitRecord);
		
		/***************************************************************/
		System.out.println("Is fault? " + fault);
		System.out.println("Hit score: " + this.currentHitScore);
		/***************************************************************/
		
		/* Update current player */
		if (fault) this.currentPlayer = this.getAnotherPlayer();
		
		/* update machine state. */
		updateState(fault);
		
		
		
		/*********************  Trace info  ************************/
		/* Print some info so that I can know more. */
		if (this.getCurrentPlayer() == this.playerOne)
			System.out.println("Current player: player one");
		else
			System.out.println("Current player: player two");
		System.out.println("Current machine state: " + state);
		System.out.println("White ball entered? " + this.doesWhiteBallEnter());
		System.out.println("Red ball left: " + this.numOfRedBallLeft);
		System.out.println("Recorver balls: " + this.getColorBallsThatShouldBeBack());
		/********************************************************************************/
		
	}
	
	/* This method only change machine state, as the name imply*/
	private void updateState(boolean fault) {
		/* update state according to 'fault' is a clear method. */
		if (fault) {
			if (this.numOfRedBallLeft >= 0) {
				state = HitState.red;
			} else if (isThereGreenBall) {
				state = HitState.green;
			} else if (isThereYellowBall) {
				state = HitState.yellow;
			} else if (isThereBrownBall) {
				state = HitState.brown;
			} else if (isThereBlueBall) {
				state = HitState.blue;
			} else if (isTherePinkBall) {
				state = HitState.pink;
			} else if (isThereBlackBall) {
				state = HitState.black;
			} else {
				state = HitState.end;
			}
			
		} else {
			/* Well, I hit successfully. Go next state. */
			if (state == HitState.red) {
				state = HitState.multicolor;
				
			} else if (state == HitState.multicolor) {
				state = HitState.green;
				
			} else if (state == HitState.green) {
				state = HitState.yellow;
				
			} else if (state == HitState.yellow) {
				state = HitState.brown;
				
			} else if (state == HitState.brown) {
				state = HitState.blue;
				
			} else if (state == HitState.blue) {
				state = HitState.pink;
				
			} else if (state == HitState.pink) {
				state = HitState.black;
				
			} else if (state == HitState.black) {
				state = HitState.end;
			} else  {
				/* unexpected */
				TraceUtil.trace(this, "unexpected state.");
			}
		}
	}
	
	/* The most complex method in this class.
	 * 1. Initialize the state of this hit.
	 * 2. See if the white ball entered.
	 * 3. See the touch balls.
	 * 4. See the entered balls.
	 * In this process, we need record whether the hit is fault,
	 * whether the white ball is entered, color balls that need
	 * be placed back. Moreover, we need compute the score,
	 * if fault, the score is negative.
	 */
	private boolean checkFaultAndComputeScore(HitRecord ht) {
		boolean result = false;
		
		// initialize the hit score parameters
		this.currentHitScore = 0;
		this.flagOfWhiteBallEntered = false;
		this.ballsThatNeedBeBack.clear();
		
		/* see whether the white ball entered */
		if (ht.doesWhiteBallEnter()) {
			result =  true;
			this.currentHitScore = -4;
			this.flagOfWhiteBallEntered = true;
		}
		
		// this variable is used by the process that see the entered balls
		BallColor firstTouchedBall = null;
		/* see the touched balls */
		if (ht.getTouchedBalls().size() == 0) {
			System.out.println("The is nor ball touched, thus entered.");
			result = true;
			// The more a negative number is minimum, the more the absolute value is maximum 
			this.currentHitScore  = Math.min(-4, -state.getBallColor().getScore());
			
			// The player don't touch any balls, thus enter balls. I need not 
			// justify the entered balls, because of no balls entered. Return immediately.
			return result;
		} else {
			/* See whether the firstly touched ball is proper. */
			firstTouchedBall = ht.getTouchedBalls().get(0);
			if (!state.isThisBallAllowed(firstTouchedBall)) {
				result = true;
				this.currentHitScore = Math.min(-4, -firstTouchedBall.getScore());
			}
		}
		
		
		/* See the balls that entered */
		List<BallColor> enteredBalls= ht.getEnteredBalls();
		if (enteredBalls.size() > 1) {
			/* If the guy enters multiple balls, the balls are all red
			 * in order not to be fault and the hit machine's current
			 * state must be red. */
			
			if (result) {
				// The guy has already be fault
				int numRedBallEntered = 0;
				
				for (BallColor b : enteredBalls) {
					if (b != BallColor.red) {
						this.currentHitScore = Math.min(this.currentHitScore , -b.getScore());
						this.ballsThatNeedBeBack.add(b);
					} else {
						/* One red enters. */
						this.numOfRedBallLeft--;
						numRedBallEntered++;
					}
				}
				this.currentHitScore = Math.min(this.currentHitScore , -numRedBallEntered);
				
			} else {
				if (state == HitState.red) {
					/* May succeed, I hope */
					for (BallColor b : enteredBalls) {
						if (b != BallColor.red) {
							result = true;
							this.currentHitScore = Math.min(this.currentHitScore , -b.getScore());
							this.ballsThatNeedBeBack.add(b);
						} else {
							/* One red enters. */
							this.numOfRedBallLeft--;
						}
					}
					
					if (!result) {
						/* Well, the 'hit' pass the test. All ball is red. */
						this.currentHitScore = enteredBalls.size();
					}
				} else {
					/* absolutely fail! */
					result = true;
					
					for (BallColor b : enteredBalls) {
						if (b != BallColor.red) {
							this.currentHitScore = Math.min(this.currentHitScore , -b.getScore());
							this.ballsThatNeedBeBack.add(b);
						} else {
							/* One red enters. */
							this.numOfRedBallLeft--;
						}
					}
				} // end if ( state == HitState.red )
			} // end if (result)
			
			
		} else if (enteredBalls.size() == 1) {
			BallColor b = enteredBalls.get(0);
			int score = b.getScore();
			
			
			if (result) {
				// The guy has already be fault
				this.currentHitScore = Math.min(this.currentHitScore , -score);
			} else {
				// The guy is not fault for now.
				if (state.isThisBallAllowed(b)) {
					/* This ball is allowed.
					 * If the current state is a multicolor, the first touched must be the entered ball. */
					if (state == HitState.multicolor) {
						if (firstTouchedBall == b) {
							/* Well, I proceed the complex justification in the end.
							 * The hit is success */
							this.currentHitScore = score;
						} else {
							result = true;
							this.currentHitScore = Math.min(this.currentHitScore , -score);
						}
					} else {
						/* Well, I proceed the complex justification in the end.
						 * The hit is success */
						this.currentHitScore = score;
					}
					
				} else {
					result = true;
					this.currentHitScore = Math.min(this.currentHitScore , -score);
				}
				
			}
			
			
			
			/* update the balls on the desktop. */
			if (b == BallColor.red) {
				// In both success and failure case, the red ball must not be back desktop.
				this.numOfRedBallLeft--;
			} else {
				// If the ball is multicolor and the guy is fault, then recover it.
				// Otherwise if the machine state is corresponding to the ball color,
				// then the ball need not be recovered, otherwise need be.
				if (result) {
					this.ballsThatNeedBeBack.add(b);
				} else {
					if (b == BallColor.green && state == HitState.green) {
						this.isThereGreenBall = false;
					} else if (b == BallColor.yellow && state == HitState.yellow) {
						this.isThereYellowBall = false;
					} else if (b == BallColor.brown && state == HitState.brown) {
						this.isThereBrownBall = false;
					} else if (b == BallColor.blue && state == HitState.blue) {
						this.isThereBlueBall = false;
					} else if (b == BallColor.pink && state == HitState.pink) {
						this.isTherePinkBall = false;
					} else if (b == BallColor.green && state == HitState.green) {
						this.isThereBlackBall = false;
					} else {
						this.ballsThatNeedBeBack.add(b);
					}
				}
			}
			
		} else {
			
			if (result) {
				/* This guy have be fault, possibly enter white balls or touch
				 * a illegal ball, or touch no ball. */
				// this.currentHitScore = Math.min(this.currentHitScore, -4);
			} else {
				/* this guy don't enter any ball, but he have not be fault.
				 * So he is fault, but need not penalty  */
				result = true;
				this.currentHitScore = 0;
			}
		}
		
		return result;
	}

	@Override
	public BallColor getTarget() {
		return this.state.getBallColor();
	}
	
}
